package com.haier.npt.mybatis.plugins;


import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Properties;

import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeException;
import org.apache.ibatis.type.TypeHandler;

import com.haier.npt.mybatis.plugins.util.PluginUtil;


@Intercepts({
	@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class,Integer.class}),
	@Signature(type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class})
})
public class UpdateInterceptor implements Interceptor {
	
	private static final Log log = LogFactory.getLog(UpdateInterceptor.class);
	private Properties props = null;
	
	@Override
	public Object intercept(Invocation invocation) throws Exception {
		
		String lockerTimeColumn;
		if(null == props || props.isEmpty()) {
			lockerTimeColumn = "locker_time";
		} else {
			lockerTimeColumn = props.getProperty("lockerTime", "locker_time");
		}
		
		String interceptMethod = invocation.getMethod().getName();
		
		
		if("prepare".equals(interceptMethod)) {
			
			StatementHandler routingHandler = (StatementHandler) PluginUtil.processTarget(invocation.getTarget());
			MetaObject routingMeta = SystemMetaObject.forObject(routingHandler);
			MetaObject hm = routingMeta.metaObjectForProperty("delegate");
			
			//如果操作方法不是update，则直接调用原方法处理
			MappedStatement ms = (MappedStatement) hm.getValue("mappedStatement");
			if(ms.getSqlCommandType() != SqlCommandType.UPDATE){
				return invocation.proceed();
			}
	
			String originalSql = (String) hm.getValue("boundSql.sql");
			StringBuilder builder = new StringBuilder(originalSql);
			builder.append(" AND ");
			builder.append(lockerTimeColumn);
			builder.append(" = ?");
			hm.setValue("boundSql.sql", builder.toString());
			
		} else if("setParameters".equals(interceptMethod)) {
			
			ParameterHandler handler = (ParameterHandler) PluginUtil.processTarget(invocation.getTarget());
			MetaObject hm = SystemMetaObject.forObject(handler);
			
			//如果操作方法不是update，则直接调用原方法处理
			MappedStatement ms = (MappedStatement) hm.getValue("mappedStatement");
			if(ms.getSqlCommandType() != SqlCommandType.UPDATE){
				return invocation.proceed();
			}
			
			//取出所有参数并判断字段中是否包含时间戳字段
			BoundSql boundSql = (BoundSql) hm.getValue("boundSql");
			Object parameterObject = boundSql.getParameterObject();
			if(parameterObject instanceof MapperMethod.ParamMap<?>) {
				MapperMethod.ParamMap<?> paramMap = (MapperMethod.ParamMap<?>) parameterObject;
				if(!paramMap.containsKey(lockerTimeColumn)) {
					throw new TypeException("All the primitive type parameters must add MyBatis's @Param Annotaion");
				}
			}
			
			//从时间戳字段中取出原始值，并在参数列表中新增一个时间戳字段
			Configuration configuration = ((MappedStatement) hm.getValue("mappedStatement")).getConfiguration();
			MetaObject pm = configuration.newMetaObject(parameterObject);
	        Object value = pm.getValue(lockerTimeColumn);
	        ParameterMapping versionMapping = new ParameterMapping.Builder(configuration, lockerTimeColumn, Object.class).build();
			TypeHandler typeHandler = versionMapping.getTypeHandler();
	        JdbcType jdbcType = versionMapping.getJdbcType();
	        
	        if (value == null && jdbcType == null) {
	        	 jdbcType = configuration.getJdbcTypeForNull();
	        }
	        
	        int timeStampLocation = boundSql.getParameterMappings().size() + 1;
	 		try {
	 			PreparedStatement ps = (PreparedStatement) invocation.getArgs()[0];
	 			typeHandler.setParameter(ps, timeStampLocation, value, jdbcType);
	 		} catch (TypeException | SQLException e) {
	 			throw new TypeException("set parameter 'time_stamp' faild, Cause: " + e, e);
	 		}
	 		
	 		if(value.getClass() != Timestamp.class) {
	 			if(log.isDebugEnabled()) {
	 				log.error("property type error, the type of locker_time property must be Timestamp.");
	 			}
	 		}
	 		
	 		//新加入的时间戳值设为当前时间
	 		pm.setValue(lockerTimeColumn, System.currentTimeMillis());
		}
		return invocation.proceed();
	}

	@Override
	public Object plugin(Object target) {
		if (target instanceof StatementHandler || target instanceof ParameterHandler)
            return Plugin.wrap(target, this);
		return target;
	}

	@Override
	public void setProperties(Properties properties) {
		if(null != properties && !properties.isEmpty()) props = properties;
	}

}